/*
	This file is part of ADBCJ.

	ADBCJ is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	ADBCJ is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with ADBCJ.  If not, see <http://www.gnu.org/licenses/>.

	Copyright 2008  Mike Heath
 */
package com.run.go.gearbox.mysql.codec;

import com.run.go.gearbox.mysql.codec.parse.DecoderState;
import com.run.go.gearbox.mysql.io.BoundedInputStream;
import com.run.go.gearbox.mysql.io.IoUtils;
import com.run.go.gearbox.mysql.response.FailedToParseInput;
import com.run.go.gearbox.mysql.response.ServerPacket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;

/**
 * Client stateful decoder.  Being stateful, each client connection must have its own decoder instance to function
 * properly.
 *
 * @author Mike Heath <mheath@apache.org>
 */
public class MySqlDecoder implements Decoder {

    private static final Logger logger = LoggerFactory.getLogger(Decoder.class);

    private DecoderState state;

    public MySqlDecoder(DecoderState state){
        this.state = state;
    }


    public Object decode(InputStream input, Channel channel) throws IOException {

        // TODO This should be the max packet size - make this configurable
        input.mark(Integer.MAX_VALUE);
        ServerPacket message = null;
        try {
            message = doDecode(input, channel);
        } finally {
            if (message == null) {
                input.reset();
            }
        }
        return message;
    }



    protected ServerPacket doDecode(InputStream input, Channel channel) throws IOException {
        // If we can't block, make sure there's enough data available to read
        if (input.available() < 3) {
            return null;
        }
        // Read the packet length
        final int length = IoUtils.readUnsignedMediumInt(input);

        // Make sure we have enough data for the packet length and the packet number
        if (input.available() < length + 1) {
            return null;
        }
        final int packetNumber = IoUtils.safeRead(input);

        if(logger.isDebugEnabled()) {
            ByteBuf dumpBuf = null;
            try {
                input.mark(Integer.MAX_VALUE);
                dumpBuf = channel.alloc()
                        .buffer(length + 4)
                        .writeMedium(length)
                        .writeByte(packetNumber);
                dumpBuf.writeBytes(input, length);
                logger.debug("Received packet: \n{}", ByteBufUtil.prettyHexDump(dumpBuf));
            } finally {
                if(dumpBuf != null) {
                    dumpBuf.release();
                }
                input.reset();
            }
        }

        // Dump packet for debug
        final BoundedInputStream in = new BoundedInputStream(input, length);
        logger.trace("Decoding in state {}", state);

        in.read();

        final ServerPacket stateAndResult = state.parse(length, packetNumber, in, channel);

        final int rem = in.getRemaining();
        if (rem > 0) {
            final String message = "Didn't read all input. Maybe this input belongs to a failed request. " +
                    "Remaining bytes: " + rem;
            return new FailedToParseInput(length, packetNumber, new IllegalStateException(message));
        }

        return stateAndResult;
    }
}
